package com.heo.client.app;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.InetSocketAddress;
import java.util.Date;

import javax.swing.AbstractListModel;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.heo.protocol.packet.BasePacket;
import com.heo.protocol.packet.EditDevicePasswordReq;
import com.heo.protocol.packet.FirmwareDownloadReq;
import com.heo.protocol.packet.GetDeviceCodeReq;
import com.heo.protocol.packet.GetDeviceParaReq;
import com.heo.protocol.packet.HeartBySignalReq;
import com.heo.protocol.packet.HeartReq;
import com.heo.protocol.packet.LoginOutReq;
import com.heo.protocol.packet.LoginReq;
import com.heo.protocol.packet.PutCoinReq;
import com.heo.protocol.packet.ReadDeviceCodeReq;
import com.heo.protocol.packet.ReportDeviceStatusReq;
import com.heo.protocol.packet.UploadLogReq;
import com.heo.pub.ClientConfig;
import com.heo.server.SocketServerBootstrap;
import com.heo.util.SequenceHelper;
import com.heo.util.TimeHelper;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;

public class DeviceWindow
{
  private static final String fontStyle = "微软雅黑";
  private Logger log = Logger.getLogger(this.getClass());
  private JFrame frame;
  private JTextField ipField;
  private JTextField portField;
  private JTextField imeiField;
  private JTextField passwordField;
  private JTextField firmwareVerField;
  private JTextField protocolVerField;
  private JTextArea consoleArea = new JTextArea();
  private JList<String> workList = new JList<String>();
  private JList<String> startResult = new JList<String>();
  private JList<String> deviceTypeField = new JList<String>();
  private JCheckBox autoResponse = new JCheckBox("模拟工作过程");
  private static String lineWrap = "\r\n";

  private static int index = 0;
  private static boolean isWork = false;
  private JTextField channelIndexField;

  private static DeviceWindow window;
  private JTextField startField;
  private JTextField downLengthField;
  private JList<String> loginTypeField = new JList<String>();

  public JList<String> getWorkList()
  {
    return workList;
  }

  public JTextField getChannelIndexField()
  {
    return channelIndexField;
  }

  public int getStartResult()
  {
    return startResult.getSelectedIndex();
  }

  public JTextArea getConsoleArea()
  {
    return consoleArea;
  }

  public boolean getAutoResponse()
  {
    return autoResponse.isSelected();
  }

  /**
   * Launch the application.
   */
  public static void main(String[] args)
  {
    PropertyConfigurator.configure(SocketServerBootstrap.class.getResource("/").getPath() + "/com/heo/config/log4j.properties");
    EventQueue.invokeLater(new Runnable()
    {
      public void run()
      {
        try
        {
          window = new DeviceWindow();
          window.frame.setVisible(true);
        }
        catch (Exception e)
        {
          e.printStackTrace();
        }
      }
    });
  }

  private String now()
  {
    return TimeHelper.dateTimeToStr(new Date()) + " - ";
  }

  /**
   * Create the application.
   */
  public DeviceWindow()
  {
    initialize();
  }

  /**
   * Initialize the contents of the frame.
   */
  private void initialize()
  {
    frame = new JFrame();
    frame.setResizable(false);
    frame.setTitle("硬件模拟器");
    frame.setBounds(100, 100, 1200, 900);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);

    JSplitPane splitPane = new JSplitPane();
    splitPane.setPreferredSize(new Dimension(330, 25));
    splitPane.setMinimumSize(new Dimension(330, 25));
    splitPane.setContinuousLayout(true);
    frame.getContentPane().add(splitPane, BorderLayout.CENTER);

    JScrollPane scrollPane = new JScrollPane();
    splitPane.setRightComponent(scrollPane);

    consoleArea.setWrapStyleWord(true);
    consoleArea.setFont(new Font(fontStyle, Font.PLAIN, 13));
    consoleArea.setLineWrap(true);
    consoleArea.getDocument().addDocumentListener(new DocumentListener()
    {
      @Override
      public void insertUpdate(DocumentEvent e)
      {
        // 内容有更新时，就滚动到最下面
        consoleArea.setCaretPosition(consoleArea.getText().length());
      }

      @Override
      public void removeUpdate(DocumentEvent e)
      {
      }

      @Override
      public void changedUpdate(DocumentEvent e)
      {
      }
    });
    initConsoleArea();

    scrollPane.setViewportView(consoleArea);

    JPanel panel = new JPanel();
    splitPane.setLeftComponent(panel);

    JLabel lblNewLabel = new JLabel("服务器地址：");
    lblNewLabel.setFont(new Font(fontStyle, Font.PLAIN, 12));

    ipField = new JTextField();
    ipField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    ipField.setColumns(10);
    ipField.setText(ClientConfig.SERVER_IP);

    JLabel lblNewLabel_1 = new JLabel("服务器端口：");
    lblNewLabel_1.setFont(new Font(fontStyle, Font.PLAIN, 12));

    portField = new JTextField();
    portField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    portField.setColumns(10);
    portField.setText(String.valueOf(ClientConfig.PORT));

    JLabel lblNewLabel_2 = new JLabel("登录名称：");
    lblNewLabel_2.setFont(new Font(fontStyle, Font.PLAIN, 12));

    imeiField = new JTextField();
    imeiField.setText("111111111111111");
    imeiField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    imeiField.setColumns(10);

    JLabel lblNewLabel_3 = new JLabel("设备类型：");
    lblNewLabel_3.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel lblNewLabel_4 = new JLabel("设备密码：");
    lblNewLabel_4.setFont(new Font(fontStyle, Font.PLAIN, 12));

    passwordField = new JTextField();
    passwordField.setText("abcd");
    passwordField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    passwordField.setColumns(10);

    workList.setFont(new Font(fontStyle, Font.PLAIN, 12));
    workList.setModel(new AbstractListModel()
    {
      String[] values = new String[]
      { "空闲", "启动成功", "工作中", "工作结束", "故障" };

      public int getSize()
      {
        return values.length;
      }

      public Object getElementAt(int index)
      {
        return values[index];
      }
    });
    workList.setSelectedIndex(0);
    workList.setToolTipText("");

    JButton activeBtn = new JButton("激活链路");
    activeBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        AppClientBootstrap client = new AppClientBootstrap(ipField.getText(), Integer.valueOf(portField.getText()), index, window);
        index++;
        Thread thread = new Thread(client);
        thread.start();
      }
    });
    activeBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton loginBtn = new JButton("登录请求");
    loginBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        sendLoginPacket();
      }
    });
    loginBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton loginoutBtn = new JButton("断开链路");
    loginoutBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        sendLoginOutPacket();
      }
    });
    loginoutBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton heartBtn = new JButton("心跳保持");
    heartBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        sendHeartPacket();
      }
    });
    heartBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton startWashBtn = new JButton("模拟洗衣");
    startWashBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        isWork = true;
        startWash();
      }
    });
    startWashBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton putCoinBtn = new JButton("投币请求");
    putCoinBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        sendPutCoinPacket();
      }
    });
    putCoinBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label = new JLabel("固件版本：");
    label.setFont(new Font(fontStyle, Font.PLAIN, 12));

    firmwareVerField = new JTextField();
    firmwareVerField.setText("1.00");
    firmwareVerField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    firmwareVerField.setColumns(10);

    JLabel label_1 = new JLabel("协议版本：");
    label_1.setFont(new Font(fontStyle, Font.PLAIN, 12));

    protocolVerField = new JTextField();
    protocolVerField.setText("1.00");
    protocolVerField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    protocolVerField.setColumns(10);

    JLabel label_2 = new JLabel("状态上报：");
    label_2.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_3 = new JLabel("(20位字符)");
    label_3.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_4 = new JLabel("(1位数字)");
    label_4.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_5 = new JLabel("(4位字符)");
    label_5.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_6 = new JLabel("(数字)");
    label_6.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel lblip = new JLabel("(ip地址)");
    lblip.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_7 = new JLabel("(1位数字)");
    label_7.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_8 = new JLabel("(1位数字)");
    label_8.setFont(new Font(fontStyle, Font.PLAIN, 12));

    deviceTypeField.setModel(new AbstractListModel<String>()
    {
      private static final long serialVersionUID = 1L;
      String[] values = new String[]
      { "洗衣机" };

      public int getSize()
      {
        return values.length;
      }

      public String getElementAt(int index)
      {
        return values[index];
      }
    });
    deviceTypeField.setSelectedIndex(0);
    deviceTypeField.setToolTipText("");
    deviceTypeField.setFont(new Font(fontStyle, Font.PLAIN, 12));

    channelIndexField = new JTextField();
    channelIndexField.setText("0");
    channelIndexField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    channelIndexField.setColumns(10);

    JLabel label_9 = new JLabel("通道编号：");
    label_9.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton reportStatusBtn = new JButton("状态上报");
    reportStatusBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        sendReportStatus();
      }
    });
    reportStatusBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_10 = new JLabel("启动结果：");
    label_10.setFont(new Font(fontStyle, Font.PLAIN, 12));

    startResult.setToolTipText("");
    startResult.setModel(new AbstractListModel()
    {
      String[] values = new String[]
      { "启动失败", "启动成功" };

      public int getSize()
      {
        return values.length;
      }

      public Object getElementAt(int index)
      {
        return values[index];
      }
    });
    startResult.setSelectedIndex(1);
    startResult.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_11 = new JLabel("(1位数字)");
    label_11.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_12 = new JLabel("(3位字符)");
    label_12.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_13 = new JLabel("(3位字符)");
    label_13.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton uploadLogBtn = new JButton("上传日志");
    uploadLogBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        uploadLogPacket();
      }
    });
    uploadLogBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton downFirmwareBtn = new JButton("下载固件");
    downFirmwareBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        downloadFirmwarePacket();
      }
    });
    downFirmwareBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    startField = new JTextField();
    startField.setText("0");
    startField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    startField.setColumns(10);

    downLengthField = new JTextField();
    downLengthField.setText("0");
    downLengthField.setFont(new Font(fontStyle, Font.PLAIN, 12));
    downLengthField.setColumns(10);

    JLabel label_15 = new JLabel("下载长度：");
    label_15.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_14 = new JLabel("下载偏移量：");
    label_14.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_16 = new JLabel("工作模拟：");
    label_16.setFont(new Font(fontStyle, Font.PLAIN, 12));

    autoResponse.setFont(new Font(fontStyle, Font.PLAIN, 12));
    autoResponse.setSelected(true);

    JButton getDeviceCodeBtn = new JButton("取二维码");
    getDeviceCodeBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        getDeviceCodePacket();
      }
    });
    getDeviceCodeBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JButton readDeviceCodeBtn = new JButton("反向扫码");
    readDeviceCodeBtn.addMouseListener(new MouseAdapter()
    {
      @Override
      public void mouseClicked(MouseEvent e)
      {
        readDeviceCodePacket();
      }
    });
    readDeviceCodeBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    loginTypeField.setToolTipText("");
    loginTypeField.setModel(new AbstractListModel()
    {
      String[] values = new String[]
      { "IMEI号登录", "MAC登录", "SN登录" };

      public int getSize()
      {
        return values.length;
      }

      public Object getElementAt(int index)
      {
        return values[index];
      }
    });
    loginTypeField.setSelectedIndex(0);
    loginTypeField.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_17 = new JLabel("(1位数字)");
    label_17.setFont(new Font(fontStyle, Font.PLAIN, 12));

    JLabel label_18 = new JLabel("登录类型：");
    label_18.setFont(new Font(fontStyle, Font.PLAIN, 12));
    
    JButton editPasswordBtn = new JButton("修改密码");
    editPasswordBtn.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        sendEditPasswordPacket();
      }
    });
    editPasswordBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));
    
    JButton updateParaBtn = new JButton("更新参数");
    updateParaBtn.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        updateParaPacket();
      }
    });
    updateParaBtn.setFont(new Font(fontStyle, Font.PLAIN, 12));

    GroupLayout gl_panel = new GroupLayout(panel);
    gl_panel.setHorizontalGroup(
      gl_panel.createParallelGroup(Alignment.LEADING)
        .addGroup(gl_panel.createSequentialGroup()
          .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
            .addGroup(gl_panel.createSequentialGroup()
              .addGap(19)
              .addGroup(gl_panel.createParallelGroup(Alignment.TRAILING)
                .addComponent(lblNewLabel)
                .addComponent(lblNewLabel_1)
                .addComponent(label_9, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(lblNewLabel_4)
                .addComponent(label, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_1, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_2, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_10, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_14, GroupLayout.PREFERRED_SIZE, 75, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_15, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(label_16, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                .addComponent(lblNewLabel_2)
                .addGroup(gl_panel.createParallelGroup(Alignment.LEADING, false)
                  .addComponent(label_18, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                  .addComponent(lblNewLabel_3, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addComponent(activeBtn)
                .addComponent(heartBtn)
                .addComponent(downFirmwareBtn)
                .addComponent(editPasswordBtn))
              .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
                .addGroup(gl_panel.createSequentialGroup()
                  .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
                    .addGroup(gl_panel.createSequentialGroup()
                      .addGap(17)
                      .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
                        .addComponent(channelIndexField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(downLengthField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(startResult, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(workList, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(protocolVerField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(passwordField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(firmwareVerField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(autoResponse, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(deviceTypeField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(loginTypeField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(portField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(ipField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                        .addComponent(startField, GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)))
                    .addGroup(gl_panel.createSequentialGroup()
                      .addGap(18)
                      .addComponent(imeiField, GroupLayout.PREFERRED_SIZE, 139, GroupLayout.PREFERRED_SIZE)))
                  .addPreferredGap(ComponentPlacement.RELATED)
                  .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
                    .addComponent(label_17, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_3, GroupLayout.PREFERRED_SIZE, 71, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_11, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_8, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_13, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_12, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_5, GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE)
                    .addComponent(label_7, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(label_4)
                    .addComponent(label_6, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)
                    .addComponent(lblip, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE)))
                .addGroup(gl_panel.createSequentialGroup()
                  .addGap(18)
                  .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
                    .addGroup(gl_panel.createSequentialGroup()
                      .addComponent(putCoinBtn)
                      .addGap(18)
                      .addComponent(uploadLogBtn))
                    .addGroup(gl_panel.createSequentialGroup()
                      .addComponent(loginBtn)
                      .addGap(18)
                      .addComponent(loginoutBtn))
                    .addGroup(gl_panel.createSequentialGroup()
                      .addComponent(getDeviceCodeBtn)
                      .addGap(18)
                      .addComponent(readDeviceCodeBtn))))
                .addGroup(gl_panel.createSequentialGroup()
                  .addGap(18)
                  .addComponent(updateParaBtn))))
            .addGroup(gl_panel.createSequentialGroup()
              .addGap(118)
              .addComponent(reportStatusBtn))
            .addGroup(gl_panel.createSequentialGroup()
              .addGap(19)
              .addComponent(startWashBtn)
              .addGap(229)))
          .addContainerGap())
    );
    gl_panel.setVerticalGroup(
      gl_panel.createParallelGroup(Alignment.LEADING)
        .addGroup(gl_panel.createSequentialGroup()
          .addContainerGap()
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(lblNewLabel)
            .addComponent(ipField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .addComponent(lblip))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(portField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .addComponent(lblNewLabel_1)
            .addComponent(label_6))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(lblNewLabel_2)
            .addComponent(imeiField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_3))
          .addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
            .addGroup(gl_panel.createSequentialGroup()
              .addPreferredGap(ComponentPlacement.RELATED)
              .addComponent(loginTypeField, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE))
            .addGroup(gl_panel.createSequentialGroup()
              .addGap(17)
              .addComponent(label_17, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
            .addGroup(gl_panel.createSequentialGroup()
              .addGap(18)
              .addComponent(label_18, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)))
          .addPreferredGap(ComponentPlacement.RELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(deviceTypeField, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)
            .addComponent(lblNewLabel_3)
            .addComponent(label_4))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(label_9, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
            .addComponent(channelIndexField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_7))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(lblNewLabel_4)
            .addComponent(passwordField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_5))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(label_12, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
            .addComponent(label, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
            .addComponent(firmwareVerField, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(label_1, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
            .addComponent(protocolVerField, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_13, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(workList)
            .addComponent(label_8)
            .addComponent(label_2, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(startResult, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_10, GroupLayout.DEFAULT_SIZE, 17, Short.MAX_VALUE)
            .addComponent(label_11, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.RELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(startField, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_14, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.RELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(downLengthField, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE)
            .addComponent(label_15, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE))
          .addPreferredGap(ComponentPlacement.RELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(label_16, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
            .addComponent(autoResponse))
          .addGap(18)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(activeBtn)
            .addComponent(loginBtn)
            .addComponent(loginoutBtn))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(heartBtn)
            .addComponent(putCoinBtn)
            .addComponent(uploadLogBtn))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(downFirmwareBtn)
            .addComponent(getDeviceCodeBtn)
            .addComponent(readDeviceCodeBtn))
          .addPreferredGap(ComponentPlacement.UNRELATED)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(editPasswordBtn)
            .addComponent(updateParaBtn))
          .addGap(45)
          .addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
            .addComponent(startWashBtn)
            .addComponent(reportStatusBtn))
          .addContainerGap())
    );
    panel.setLayout(gl_panel);
    splitPane.setDividerLocation(330);
  }

  /**
   * 给当前所有的链路 发送状态上报
   */
  private void sendReportStatus()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          ReportDeviceStatusReq rdsr = new ReportDeviceStatusReq();
          rdsr.setSendCount((byte) 0x01);
          rdsr.setSequenceId(SequenceHelper.getId(channelId));
          rdsr.setChannelIndex(Integer.valueOf(channelIndexField.getText()));
          rdsr.setChannelStatus((short) workList.getSelectedIndex());
          log.debug(rdsr.toString());
          sendPacket(i, channel, rdsr);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送状态上报：" + rdsr.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 请求下载固件
   */
  private void downloadFirmwarePacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          FirmwareDownloadReq downReq = new FirmwareDownloadReq();
          downReq.setStartIndex(Integer.valueOf(startField.getText()));
          downReq.setDownLength(Integer.valueOf(downLengthField.getText()));
          sendPacket(i, channel, downReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]请求下载固件，起始位置：" + startField.getText() + "，下载长度：" + downLengthField.getText() + " 数据包内容：" + downReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 获得设备二维码
   */
  private void getDeviceCodePacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          GetDeviceCodeReq getCodeReq = new GetDeviceCodeReq();
          getCodeReq.setChannelIndex(Integer.valueOf(channelIndexField.getText()));
          sendPacket(i, channel, getCodeReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]请求获得设备（通道号：" + channelIndexField.getText() + "）二维码。" + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 反向扫码
   */
  private void readDeviceCodePacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          ReadDeviceCodeReq readCodeReq = new ReadDeviceCodeReq();
          readCodeReq.setChannelIndex(Integer.valueOf(channelIndexField.getText()));
          readCodeReq.setCodeContent("abcdefghijklmn");
          sendPacket(i, channel, readCodeReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送反向扫码结果（发送扫码内容：“abcdefghijklmn”）。" + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 上传设备日志
   */
  private void uploadLogPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          String log = "device log upload test.";
          UploadLogReq uploadLogReq = new UploadLogReq(log);
          uploadLogReq.setSequenceId(SequenceHelper.getId(channelId));
          uploadLogReq.setLogContent(log);
          sendPacket(i, channel, uploadLogReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送上传设备日志请求，上传内容：->" + log + " " + uploadLogReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 给当前所有的链路 发送投币消息
   */
  private void sendPutCoinPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          PutCoinReq putCoinReq = new PutCoinReq();
          log.debug(putCoinReq.toString());
          sendPacket(i, channel, putCoinReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送投币请求：" + putCoinReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }
  
  /**
   * 更新设备参数
   */
  private void updateParaPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          String iemi = imeiField.getText();
          GetDeviceParaReq getDeviceParaReq = new GetDeviceParaReq();
          getDeviceParaReq.setHardVer(firmwareVerField.getText());
          getDeviceParaReq.setSimId(iemi);
          getDeviceParaReq.setDeviceSn(iemi);
          getDeviceParaReq.setDeviceMac(iemi);
          log.debug(getDeviceParaReq.toString());

          sendPacket(i, channel, getDeviceParaReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送更新设备参数请求：" + getDeviceParaReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }
  
  /**
   * 发送修改密码消息
   */
  private void sendEditPasswordPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          EditDevicePasswordReq editDevicePasswordReq = new EditDevicePasswordReq();
          log.debug(editDevicePasswordReq.toString());
          editDevicePasswordReq.setPassword(passwordField.getText());
          sendPacket(i, channel, editDevicePasswordReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送修改密码请求：" + editDevicePasswordReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 给当前所有的链路 发送心跳消息
   */
  private void sendHeartPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          HeartBySignalReq heartReq = new HeartBySignalReq(channelId);
          heartReq.setSignal((short)0xFF);
          log.debug(heartReq.toString());
          sendPacket(i, channel, heartReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送心跳请求：" + heartReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 给当前所有的链路 发送断开消息
   */
  private void sendLoginOutPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        try
        {
          LoginOutReq loginOutReq = new LoginOutReq(channelId);
          log.debug(loginOutReq.toString());
          sendPacket(i, channel, loginOutReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送退出登录请求。" + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 给当前所有的链路 发送登录消息
   */
  private void sendLoginPacket()
  {
    for (int i = 0; i < index; i++)
    {
      Channel channel = ClientSocketChannelMap.getChannel(i);
      if (channel != null)
      {
        String channelId = channel.id().asShortText();
        String iemi = imeiField.getText();
        short loginType = Short.valueOf((short) loginTypeField.getSelectedIndex());
        short deviceType = Short.valueOf((short) (deviceTypeField.getSelectedIndex() + 1));
        String devicePass = passwordField.getText();
        String firmwareVer = firmwareVerField.getText();
        String protocolVer = protocolVerField.getText();
        LoginReq loginReq = null;
        try
        {
          loginReq = new LoginReq(iemi, loginType, deviceType, devicePass, firmwareVer, protocolVer);
          log.debug(loginReq.toString());
          sendPacket(i, channel, loginReq);
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]发送登录请求：" + loginReq.toString() + lineWrap);
        }
        catch (Exception e)
        {
          e.printStackTrace();
          consoleArea.append(now() + "[client][" + i + "][" + channelId + "]生成请求参数异常。" + lineWrap);
        }
      }
    }
  }

  /**
   * 发送数据包
   * 
   * @param ctx
   * @param packet
   */
  private void sendPacket(Integer index, Channel channel, BasePacket packet)
  {
    String channelId = channel.id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[client][" + channelId + "][" + remoteIpAddress + "]";

    channel.writeAndFlush(packet).addListener(new ChannelFutureListener()
    {
      // 数据发送完毕后，触发本事件
      @Override
      public void operationComplete(ChannelFuture future) throws Exception
      {
        // log.debug(logHeader + packet.getPacketName() + "发送数据成功。");
        // consoleArea.append(now() + "[client][" + index + "][" + channelId +
        // "]发送数据成功。数据内容：" + packet.toString() + lineWrap);
      }
    });
  }

  private void startWash()
  {
    try
    {

      AppClientBootstrap client = new AppClientBootstrap(ipField.getText(), Integer.valueOf(portField.getText()), index, window);
      index++;
      Thread thread = new Thread(client);
      thread.start();

      Thread.sleep(500);

      // 发送登录请求
      sendLoginPacket();

      Thread.sleep(500);

      // 启动一个心跳线程，每分钟检测一次。
      new Thread()
      {
        public void run()
        {
          while (isWork)
          {
            try
            {
              Thread.sleep(30 * 1000);
              sendHeartPacket();
            }
            catch (InterruptedException e)
            {
              consoleArea.append(e.getMessage());
              log.error(e.getMessage(), e);
            }
          }
        }
      }.start();
    }
    catch (Exception e)
    {
      consoleArea.append(e.getMessage());
    }
  }

  private void initConsoleArea()
  {
    String str = "使用方法：" + lineWrap;
    str = str + "1.点击“模拟洗衣”按钮，启动洗衣机。此时，洗衣机为空闲状态。" + lineWrap;
    str = str + "2.操作app进行支付，支付完成后，洗衣机会收到启动指令。" + lineWrap;
    str = str + "3.如果需要洗衣机返回启动成功指令，请在“启动结果”下拉框处，选择“启动成功”选项。" + lineWrap;
    str = str + "4.当洗衣机返回启动成功后，app会收到推送消息。" + lineWrap;
    str = str + "5.洗衣过程中，如需向服务器发送洗衣机的工作状态，请在“状态上报”栏目中，选择对应的工作状态，然后点击“状态上报”按钮。该状态会及时推送给app。" + lineWrap;
    str = str + "6.如需重启设备，请关闭本软件再重新打开。" + lineWrap;
    str = str + "7.其他按钮为调试功能时使用，在设备工作时，请勿使用，否则可能会工作异常。" + lineWrap;
    str = str + "8.勾选“模拟工作过程”后，设备收到启动指令时，会自动模拟正常的工作过程。" + lineWrap;
    str = str + "9.选择不同的登录类型，将以不同的方式登录。登录名最长20位，如果不足20位，则前补0x00" + lineWrap;

    str = str + "" + lineWrap;
    str = str + "注意事项：" + lineWrap;
    str = str + "1.同一时间，只允许一个设备登录，如果同一个设备2次登录，则前面登录的设备会被服务器踢下线。仅保留后面的设备。" + lineWrap;
    str = str + "2.目前可用的设备IMEI号为111111111111111、111111111111112、111111111111113" + lineWrap;
    str = str + "3.设备的密码统一为abcd，使用其他密码会导致设备登录失败。" + lineWrap;
    str = str + "" + lineWrap;

    consoleArea.append(str);
  }
}
